﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Security.Cryptography;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Globalization;

namespace ReliableBackup
{
    public partial class ReliableBackup : Form
    {
        const int maximumNumAttempts = 2;
        List<string> blackList = new List<string>() {@":\$RECYCLE.BIN", @":\System Volume Information"};
        string sourceDirectory;
        string destinationDirectory;
        bool ignoreFilesPresentInBothDirectories = false;
        int ignoreFilesLargerThan = -1;
        CancellationTokenSource cancelToken = new CancellationTokenSource();

        private struct sourceAndDestinationHashes
        {
            public string sourceHash;
            public string destinationHash;
        }

        private struct directoryWithAttributes
        {
            public string directory;
            public FileAttributes attributes;
        }

        private struct fileWithSizeAndAttributes
        {
            public string file;
            public Int64 size;
            public FileAttributes attributes;
        }

        public ReliableBackup()
        {
            InitializeComponent();
            textBoxSource.Text = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
        }

        private void setDirectories()
        {
            sourceDirectory = textBoxSource.Text;
            destinationDirectory = textBoxDestination.Text;
        }

        private void clearTextBoxLog()
        {
            textBoxLog.Clear();
        }

        private void writeTextBoxLog(string line)
        {
            textBoxLog.AppendText(line + Environment.NewLine);
        }

        private void buttonBrowseSource_Click(object sender, EventArgs e)
        {
            folderBrowserDialogSource.Description = "Please select the source folder to be backed up:";
            DialogResult result = folderBrowserDialogSource.ShowDialog();
	        if (result == DialogResult.OK)
	        {
                textBoxSource.Text = folderBrowserDialogSource.SelectedPath;
	        }
        }

        private void buttonBrowseDestination_Click(object sender, EventArgs e)
        {
            folderBrowserDialogDestination.Description = "Please select the destination folder where the backup will be done:";
            DialogResult result = folderBrowserDialogDestination.ShowDialog();
            if (result == DialogResult.OK)
            {
                textBoxDestination.Text = folderBrowserDialogDestination.SelectedPath;
            }
        }

        private void checkBoxIgnorePresentInBoth_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBoxIgnorePresentInBoth.CheckState == CheckState.Checked)
            {
                textBoxLargerThan.ReadOnly = true;
            }
            else
            {
                textBoxLargerThan.ReadOnly = false;
            }
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            DialogResult confirmResult = MessageBox.Show(this, "Are you sure you want to stop the current backup operation?", "Stop confirmation", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
            if (confirmResult == DialogResult.OK)
            {
                writeTextBoxLog("Stopping backup...");
                cancelToken.Cancel();
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            cancelToken = new CancellationTokenSource();
            setDirectories();

            ignoreFilesPresentInBothDirectories = checkBoxIgnorePresentInBoth.Checked;
            bool parseSuccessful = Int32.TryParse(textBoxLargerThan.Text, out ignoreFilesLargerThan);
            if ((parseSuccessful == false && textBoxLargerThan.Text != String.Empty) || (parseSuccessful == true && ignoreFilesLargerThan <= 0))
            {
                ignoreFilesLargerThan = -1;
                MessageBox.Show(this, String.Format("The value you entered in the \"Ignore files present in both directories larger than:\" text box ({0}) is not valid, ignoring. Only positive integers are allowed.", textBoxLargerThan.Text), "Input information", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

            if (sourceDirectory != String.Empty && destinationDirectory != String.Empty && sourceDirectory != destinationDirectory)
            {
                DialogResult confirmResult = MessageBox.Show(this, String.Format("You are about to launch a backup from:\r\n\r\n{0}\r\n\r\nTo:\r\n\r\n{1}\r\n\r\nAre you sure?", sourceDirectory, destinationDirectory), "Backup confirmation", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
                if (confirmResult == DialogResult.OK)
                {
                    doBackupAsync();
                }
            }
            else if (sourceDirectory == destinationDirectory)
                MessageBox.Show(this, "The source and destination directories are the same, please use a different destination.", "Incorrect source or destination directories", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            else
                MessageBox.Show(this, "Either the source or destination directories are missing, please fill them before launching a backup.", "Incorrect source or destination directories", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }

        private Int64 getAllDirectoriesInDirectory (string rootDirectory, List<directoryWithAttributes> directoriesFound)
        {
            Int64 directoryCount = 0;
            bool blacklistedParent = false;
            bool blacklisted = false;

            for (int i = 0; i < blackList.Count && blacklistedParent == false; i++)
            {
                blacklistedParent = rootDirectory.Contains(blackList[i]) ? true : false;
            }

            if ((File.GetAttributes(rootDirectory) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint && blacklistedParent == false)
            {
                foreach (string directory in Directory.GetDirectories(rootDirectory))
                {
                    blacklisted = false;
                    for (int i = 0; i < blackList.Count && blacklisted == false; i++)
                    {
                        blacklisted = directory.Contains(blackList[i]) ? true : false;
                    }
                    if (blacklisted == false)
                    {
                        directoryCount++;
                        directoriesFound.Add(new directoryWithAttributes() { directory = directory, attributes = File.GetAttributes(directory) });
                        directoryCount += getAllDirectoriesInDirectory(directory, directoriesFound);
                    }
                }
            }

            return directoryCount;
        }

        private Int64 getAllFilesInDirectory (string directory, List<fileWithSizeAndAttributes> filesFound)
        {
            Int64 directorySize = 0;
            bool blacklisted = false;

            for (int i = 0; i < blackList.Count && blacklisted == false; i++)
            {
                blacklisted = directory.Contains(blackList[i]) ? true : false;
            }

            if ((File.GetAttributes(directory) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint && blacklisted == false)
            {
                foreach (string file in Directory.GetFiles(directory))
                {
                    FileInfo fileInfo = new FileInfo(file);
                    directorySize += fileInfo.Length;
                    filesFound.Add(new fileWithSizeAndAttributes() { file = file, size = fileInfo.Length, attributes = fileInfo.Attributes });
                }
                foreach (string folder in Directory.GetDirectories(directory))
                {
                    directorySize += getAllFilesInDirectory(folder, filesFound);
                }
            }

            return directorySize;
        }

        private void DeleteRecursiveFolder(string directoryPath)
        {
            foreach (string subDirectory in Directory.GetDirectories(directoryPath))
            {
                DeleteRecursiveFolder(subDirectory);
            }

            foreach (string file in Directory.GetFiles(directoryPath))
            {
                File.SetAttributes(file, FileAttributes.Normal);
                File.Delete(file);
            }

            DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);
            directoryInfo.Attributes = FileAttributes.Normal;
            Directory.Delete(directoryPath);
        }



        private string calculateHash(string filePath)
        {
            SHA1 SHA1Hash = SHA1.Create();

            using (FileStream inputFile = File.OpenRead(filePath))
            {
                byte[] byteHash = SHA1Hash.ComputeHash(inputFile);
                return BitConverter.ToString(byteHash).Replace("-", "");
            }
        }

        private sourceAndDestinationHashes calculateHashesParallel(string sourceFile, string destinationFile)
        {
            string sourceHash = String.Empty;
            string destinationHash = String.Empty;

            Parallel.Invoke
            (
                () => { sourceHash = calculateHash(sourceFile); },
                () => { destinationHash = calculateHash(destinationFile); }
            );

            return new sourceAndDestinationHashes { sourceHash = sourceHash, destinationHash = destinationHash };
        }

        private void doBackupAsync()
        {
            clearTextBoxLog();

            DialogResult dialogResult;
            DateTime startTimeBackup = DateTime.Now;
            NumberFormatInfo numberFormatInfo = new NumberFormatInfo();
            numberFormatInfo.NumberDecimalSeparator = ",";
            numberFormatInfo.NumberGroupSeparator = ".";
            string applicationPath = Path.GetDirectoryName(Application.ExecutablePath);
            string outputLogPath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + ".txt";
            string outputLogDirectoriesMissingInDestinationPath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + "_DirectoriesMissingInDestination.txt";
            string outputLogDirectoriesPresentWithAttributesMismatchPath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + "_DirectoriesPresentWithAttributesMismatch.txt";
            string outputLogDirectoriesMissingInSourcePath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + "_DirectoriesMissingInSource.txt";
            string outputLogFilesMissingInDestinationPath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + "_FilesMissingInDestination.txt";
            string outputLogFilesPresentWithAttributesMismatchPath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + "_FilesPresentWithAttributesMismatch.txt";
            string outputLogFilesMissingInSourcePath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + "_FilesMissingInSource.txt";
            string outputLogFilesPresentWithHashMatchPath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + "_FilesPresentWithHashMatch.txt";
            string outputLogFilesPresentWithHashMismatchPath = applicationPath + @"\ReliableBackupLog_" + String.Format("{0:yyyyMMdd_HH-mm-ss}", startTimeBackup) + "_FilesPresentWithHashMismatch.txt";
            List<directoryWithAttributes> directoriesMissingInSource = new List<directoryWithAttributes>();
            List<directoryWithAttributes> directoriesPresentWithAttributesMismatch = new List<directoryWithAttributes>();
            List<directoryWithAttributes> directoriesMissingInDestination = new List<directoryWithAttributes>();
            List<fileWithSizeAndAttributes> filesMissingInSource = new List<fileWithSizeAndAttributes>();
            List<fileWithSizeAndAttributes> filesPresentWithAttributesMismatch = new List<fileWithSizeAndAttributes>();
            List<fileWithSizeAndAttributes> filesMissingInDestination = new List<fileWithSizeAndAttributes>();
            List<fileWithSizeAndAttributes> filesPresentWithHashMatch = new List<fileWithSizeAndAttributes>();
            List<fileWithSizeAndAttributes> filesPresentWithHashMismatch = new List<fileWithSizeAndAttributes>();

            Task doBackup = Task.Factory.StartNew(() =>
            {
                StreamWriter outputLog = new StreamWriter(outputLogPath);
                outputLog.AutoFlush = true;
                try
                {
                    checkBoxIgnorePresentInBoth.Invoke((Action)delegate { checkBoxIgnorePresentInBoth.Enabled = false; });
                    textBoxLargerThan.Invoke((Action)delegate { textBoxLargerThan.ReadOnly = true; });
                    buttonStart.Invoke((Action)delegate { buttonStart.Enabled = false; });
                    buttonStop.Invoke((Action)delegate { buttonStop.Enabled = true; });
                    buttonBrowseSource.Invoke((Action)delegate { buttonBrowseSource.Enabled = false; });
                    buttonBrowseDestination.Invoke((Action)delegate { buttonBrowseDestination.Enabled = false; });
                    textBoxSource.Invoke((Action)delegate { textBoxSource.ReadOnly = true; });
                    textBoxDestination.Invoke((Action)delegate { textBoxDestination.ReadOnly = true; });

                    List<directoryWithAttributes> sourceDirectoryList = new List<directoryWithAttributes>();
                    List<directoryWithAttributes> destinationDirectoryList = new List<directoryWithAttributes>();
                    List<fileWithSizeAndAttributes> sourceFileList = new List<fileWithSizeAndAttributes>();
                    List<fileWithSizeAndAttributes> destinationFileList = new List<fileWithSizeAndAttributes>();
                    List<string> sourceDirectoryNamesList = new List<string>();
                    List<string> destinationDirectoryNamesList = new List<string>();
                    List<string> sourceFileNamesList = new List<string>();
                    List<string> destinationFileNamesList = new List<string>();

                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Backup start time: {0:00}:{1:00}:{2:00}", startTimeBackup.Hour, startTimeBackup.Minute, startTimeBackup.Second)); });
                    outputLog.WriteLine(String.Format("Backup start time: {0:00}:{1:00}:{2:00}", startTimeBackup.Hour, startTimeBackup.Minute, startTimeBackup.Second));

                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Creating backup from {0} to {1}...", sourceDirectory, destinationDirectory)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Starting backup..."); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Analyzing..."); });
                    outputLog.WriteLine(String.Format("Creating backup from {0} to {1}...", sourceDirectory, destinationDirectory));
                    outputLog.WriteLine("Starting backup...");
                    outputLog.WriteLine("Analyzing...");
                    labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = "Status: Analyzing..."; });

                    getAllDirectoriesInDirectory(sourceDirectory, sourceDirectoryList);
                    if (cancelToken.Token.IsCancellationRequested)
                    {
                        outputLog.WriteLine("Stopping backup...");
                        cancelToken.Token.ThrowIfCancellationRequested();
                    }
                    
                    getAllDirectoriesInDirectory(destinationDirectory, destinationDirectoryList);
                    if (cancelToken.Token.IsCancellationRequested)
                    {
                        outputLog.WriteLine("Stopping backup...");
                        cancelToken.Token.ThrowIfCancellationRequested();
                    }
                    
                    Int64 sourceSizeInBytes = getAllFilesInDirectory(sourceDirectory, sourceFileList);
                    if (cancelToken.Token.IsCancellationRequested)
                    {
                        outputLog.WriteLine("Stopping backup...");
                        cancelToken.Token.ThrowIfCancellationRequested();
                    }
                    Int64 destinationSizeInBytes = getAllFilesInDirectory(destinationDirectory, destinationFileList);

                    foreach (directoryWithAttributes sourceDirectoryanalysis in sourceDirectoryList)
                    {
                        sourceDirectoryNamesList.Add(sourceDirectoryanalysis.directory);
                    }
                    foreach (directoryWithAttributes destinationDirectoryanalysis in destinationDirectoryList)
                    {
                        destinationDirectoryNamesList.Add(destinationDirectoryanalysis.directory);
                    }
                    foreach (fileWithSizeAndAttributes sourceFile in sourceFileList)
                    {
                        sourceFileNamesList.Add(sourceFile.file);
                    }
                    foreach (fileWithSizeAndAttributes destinationFile in destinationFileList)
                    {
                        destinationFileNamesList.Add(destinationFile.file);
                    }

                    using (StreamWriter outputLogDirectoriesMissingInDestination = new StreamWriter(outputLogDirectoriesMissingInDestinationPath))
                    using (StreamWriter outputLogDirectoriesPresentWithAttributesMismatch = new StreamWriter(outputLogDirectoriesPresentWithAttributesMismatchPath))
                    {
                        outputLogDirectoriesMissingInDestination.AutoFlush = true;
                        int sourceCount = sourceDirectoryList.Count;
                        int counter = 0;

                        labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Analyzing for directories missing in the destination directory. {0}/{1} directories", counter, sourceCount); });
                        progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceCount != 0 ? (int)((counter * 1000L) / sourceCount) : 0; });

                        foreach (directoryWithAttributes sourceDirectoryAnalysis in sourceDirectoryList)
                        {
                            string destinationDirectoryAnalysis = sourceDirectoryAnalysis.directory.Replace(sourceDirectory, destinationDirectory);

                            if (cancelToken.Token.IsCancellationRequested)
                            {
                                outputLog.WriteLine("Stopping backup...");
                                cancelToken.Token.ThrowIfCancellationRequested();
                            }

                            if (destinationDirectoryNamesList.Contains(destinationDirectoryAnalysis) == false)
                            {
                                outputLogDirectoriesMissingInDestination.WriteLine(String.Format("{0}", sourceDirectoryAnalysis.directory));
                                //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: Directory {0} is present in the source directory, but not in the destination directory.", sourceDirectoryAnalysis.directory)); });
                                //outputLog.WriteLine(String.Format("WARNING: Directory {0} is present in the source directory, but not in the destination directory.", sourceDirectoryAnalysis.directory));
                                directoriesMissingInDestination.Add(sourceDirectoryAnalysis);
                            }
                            else if (destinationDirectoryNamesList.Contains(destinationDirectoryAnalysis) == true
                                     && (File.GetAttributes(destinationDirectoryAnalysis) != sourceDirectoryAnalysis.attributes
                                         || Directory.GetCreationTime(sourceDirectoryAnalysis.directory) != Directory.GetCreationTime(destinationDirectoryAnalysis)
                                         || Directory.GetLastAccessTime(sourceDirectoryAnalysis.directory) != Directory.GetLastAccessTime(destinationDirectoryAnalysis)
                                         || Directory.GetLastWriteTime(sourceDirectoryAnalysis.directory) != Directory.GetLastWriteTime(destinationDirectoryAnalysis)))
                            {
                                outputLogDirectoriesPresentWithAttributesMismatch.WriteLine(String.Format("{0}", sourceDirectoryAnalysis.directory));
                                //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: Directory {0} is present in both source and destination directories, but with different attributes.", sourceDirectoryAnalysis.directory)); });
                                //outputLog.WriteLine(String.Format("WARNING: Directory {0} is present in both source and destination directories, but with different attributes.", sourceDirectoryAnalysis.directory));
                                directoriesPresentWithAttributesMismatch.Add(sourceDirectoryAnalysis);
                            }

                            counter++;
                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Analyzing for directories missing in the destination directory. {0}/{1} directories", counter, sourceCount); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceCount != 0 ? (int)((counter * 1000L) / sourceCount) : 0; });
                        }
                    }

                    using (StreamWriter outputLogDirectoriesMissingInSource = new StreamWriter(outputLogDirectoriesMissingInSourcePath))
                    {
                        outputLogDirectoriesMissingInSource.AutoFlush = true;
                        int destinationCount = destinationDirectoryList.Count;
                        int counter = 0;

                        labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Analyzing for directories missing in the source directory. {0}/{1} files", counter, destinationCount); });
                        progressBarShared.Invoke((Action)delegate { progressBarShared.Value = destinationCount != 0 ? (int)((counter * 1000L) / destinationCount) : 0; });

                        foreach (directoryWithAttributes destinationDirectoryAnalysis in destinationDirectoryList)
                        {
                            if (cancelToken.Token.IsCancellationRequested)
                            {
                                outputLog.WriteLine("Stopping backup...");
                                cancelToken.Token.ThrowIfCancellationRequested();
                            }

                            if (sourceDirectoryNamesList.Contains(destinationDirectoryAnalysis.directory.Replace(destinationDirectory, sourceDirectory)) == false)
                            {
                                outputLogDirectoriesMissingInSource.WriteLine(String.Format("{0}", destinationDirectoryAnalysis.directory));
                                //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: Directory {0} is present in the destination directory, but not in the source directory.", destinationDirectoryAnalysis.directory)); });
                                //outputLog.WriteLine(String.Format("WARNING: Directory {0} is present in the destination directory, but not in the source directory.", destinationDirectoryAnalysis.directory));
                                directoriesMissingInSource.Add(destinationDirectoryAnalysis);
                            }

                            counter++;
                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Analyzing for directories missing in the source directory. {0}/{1} files", counter, destinationCount); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = destinationCount != 0 ? (int)((counter * 1000L) / destinationCount) : 0; });
                        }
                    }

                    using (StreamWriter outputLogFilesMissingInDestination = new StreamWriter(outputLogFilesMissingInDestinationPath))
                    using (StreamWriter outputLogFilesPresentWithAttributesMismatch = new StreamWriter(outputLogFilesPresentWithAttributesMismatchPath))
                    {
                        outputLogFilesMissingInDestination.AutoFlush = true;
                        int sourceCount = sourceFileList.Count;
                        Int64 counterSize = 0;
                        int counter = 0;

                        labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Analyzing for files missing in the destination directory. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, sourceCount, ((counterSize / 1024f) / 1024f) / 1024f, ((sourceSizeInBytes / 1024f) / 1024f) / 1024f); });
                        progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceSizeInBytes != 0 ? (int)((counterSize * 1000L) / sourceSizeInBytes) : 0; });

                        foreach (fileWithSizeAndAttributes sourceFileWithSizeAndAttributes in sourceFileList)
                        {
                            string sourceFile = sourceFileWithSizeAndAttributes.file;
                            string destinationFile = sourceFile.Replace(sourceDirectory, destinationDirectory);
                            if (cancelToken.Token.IsCancellationRequested)
                            {
                                outputLog.WriteLine("Stopping backup...");
                                cancelToken.Token.ThrowIfCancellationRequested();
                            }

                            if (destinationFileNamesList.Contains(destinationFile) == false)
                            {
                                outputLogFilesMissingInDestination.WriteLine(String.Format("{0}", sourceFile));
                                //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: File {0} is present in the source directory, but not in the destination directory.", sourceFile)); });
                                //outputLog.WriteLine(String.Format("WARNING: File {0} is present in the source directory, but not in the destination directory.", sourceFile));
                                filesMissingInDestination.Add(sourceFileWithSizeAndAttributes);
                            }
                            else if (destinationFileNamesList.Contains(destinationFile) == true
                                     && (File.GetAttributes(destinationFile) != sourceFileWithSizeAndAttributes.attributes
                                         || File.GetCreationTime(sourceFile) != File.GetCreationTime(destinationFile)
                                         || File.GetLastAccessTime(sourceFile) != File.GetLastAccessTime(destinationFile)
                                         || File.GetLastWriteTime(sourceFile) != File.GetLastWriteTime(destinationFile)))
                            {
                                outputLogFilesPresentWithAttributesMismatch.WriteLine(String.Format("{0}", sourceFile));
                                //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: File {0} is present in both source and destination directories, but with different attributes.", sourceFile)); });
                                //outputLog.WriteLine(String.Format("WARNING: File {0} is present in both source and destination directories, but with different attributes.", sourceFile));
                                filesPresentWithAttributesMismatch.Add(sourceFileWithSizeAndAttributes);
                            }

                            counter++;
                            counterSize += sourceFileWithSizeAndAttributes.size;
                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Analyzing for files missing in the destination directory. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, sourceCount, ((counterSize / 1024f) / 1024f) / 1024f, ((sourceSizeInBytes / 1024f) / 1024f) / 1024f); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceSizeInBytes != 0 ? (int)((counterSize * 1000L) / sourceSizeInBytes) : 0; });
                        }
                    }

                    using (StreamWriter outputLogFilesMissingInSource = new StreamWriter(outputLogFilesMissingInSourcePath))
                    {
                        outputLogFilesMissingInSource.AutoFlush = true;
                        int destinationCount = destinationFileList.Count;
                        Int64 counterSize = 0;
                        int counter = 0;

                        labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Analyzing for files missing in the source directory. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, destinationCount, ((counterSize / 1024f) / 1024f) / 1024f, ((destinationSizeInBytes / 1024f) / 1024f) / 1024f); });
                        progressBarShared.Invoke((Action)delegate { progressBarShared.Value = destinationSizeInBytes != 0 ? (int)((counterSize * 1000L) / destinationSizeInBytes) : 0; });

                        foreach (fileWithSizeAndAttributes destinationFileWithSizeAndAttributes in destinationFileList)
                        {
                            string destinationFile = destinationFileWithSizeAndAttributes.file;
                            if (cancelToken.Token.IsCancellationRequested)
                            {
                                outputLog.WriteLine("Stopping backup...");
                                cancelToken.Token.ThrowIfCancellationRequested();
                            }

                            if (sourceFileNamesList.Contains(destinationFile.Replace(destinationDirectory, sourceDirectory)) == false)
                            {
                                outputLogFilesMissingInSource.WriteLine(String.Format("{0}", destinationFile));
                                //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: File {0} is present in the destination directory, but not in the source directory.", destinationFile)); });
                                //outputLog.WriteLine(String.Format("WARNING: File {0} is present in the destination directory, but not in the source directory.", destinationFile));
                                filesMissingInSource.Add(destinationFileWithSizeAndAttributes);
                            }

                            counter++;
                            counterSize += destinationFileWithSizeAndAttributes.size;
                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Analyzing for files missing in the source directory. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, destinationCount, ((counterSize / 1024f) / 1024f) / 1024f, ((destinationSizeInBytes / 1024f) / 1024f) / 1024f); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = destinationSizeInBytes != 0 ? (int)((counterSize * 1000L) / destinationSizeInBytes) : 0; });
                        }
                    }

                    if (ignoreFilesPresentInBothDirectories == false)
                    {
                        using (StreamWriter outputLogFilesPresentWithHashMatch = new StreamWriter(outputLogFilesPresentWithHashMatchPath))
                        using (StreamWriter outputLogFilesPresentWithHashMismatch = new StreamWriter(outputLogFilesPresentWithHashMismatchPath))
                        {
                            outputLogFilesPresentWithHashMatch.AutoFlush = true;
                            outputLogFilesPresentWithHashMismatch.AutoFlush = true;
                            int sourceCount = sourceFileList.Count;
                            Int64 counterSize = 0;
                            int counter = 0;

                            string destinationFile;

                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format(numberFormatInfo, "Status: Analyzing for files present in both source and destination directories. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, sourceCount, ((counterSize / 1024f) / 1024f) / 1024f, ((sourceSizeInBytes / 1024f) / 1024f) / 1024f); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceSizeInBytes != 0 ? (int)((counterSize * 1000L) / sourceSizeInBytes) : 0; });

                            foreach (fileWithSizeAndAttributes sourceFileWithSizeAndAttributes  in sourceFileList)
                            {
                                string sourceFile = sourceFileWithSizeAndAttributes.file;
                                if (cancelToken.Token.IsCancellationRequested)
                                {
                                    outputLog.WriteLine("Stopping backup...");
                                    cancelToken.Token.ThrowIfCancellationRequested();
                                }

                                if (((ignoreFilesLargerThan * 1024) * 1024) <= 0 || sourceFileWithSizeAndAttributes.size <= ((ignoreFilesLargerThan * 1024) * 1024))
                                {
                                    destinationFile = sourceFile.Replace(sourceDirectory, destinationDirectory);
                                    if (destinationFileNamesList.Contains(destinationFile) == true)
                                    {
                                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("File {0} is present in both source and destination directories, computing both hashes...", sourceFile)); });
                                        outputLog.WriteLine(String.Format("File {0} is present in both source and destination directories, computing both hashes...", sourceFile));

                                        sourceAndDestinationHashes hashes = calculateHashesParallel(sourceFile, destinationFile);

                                        //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Source file {0}, hash: {1}", sourceFile, hashes.sourceHash)); });
                                        outputLog.WriteLine(String.Format("Source file {0}, hash: {1}", sourceFile, hashes.sourceHash));

                                        //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Destination file {0}, hash: {1}", destinationFile, hashes.destinationHash)); });
                                        outputLog.WriteLine(String.Format("Destination file {0}, hash: {1}", destinationFile, hashes.destinationHash));

                                        if (hashes.sourceHash == hashes.destinationHash)
                                        {
                                            outputLogFilesPresentWithHashMatch.WriteLine(String.Format("{0}, hash: {1}", sourceFile, hashes.sourceHash));
                                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File hashes match."); });
                                            outputLog.WriteLine("File hashes match.");
                                            filesPresentWithHashMatch.Add(sourceFileWithSizeAndAttributes);
                                        }
                                        else
                                        {
                                            outputLogFilesPresentWithHashMismatch.WriteLine(String.Format("{0}", sourceFile));
                                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("WARNING: File hashes do not match."); });
                                            outputLog.WriteLine("WARNING: File hashes do not match.");
                                            filesPresentWithHashMismatch.Add(sourceFileWithSizeAndAttributes);
                                        }
                                    }
                                }
                                else
                                {
                                    //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("File {0} ({1}MB) is larger than the specified limit of {2}MB, skipping file...", sourceFile, (sourceFileWithSizeAndAttributes.size / 1024) / 1024, ignoreFilesLargerThan)); });
                                    outputLog.WriteLine(String.Format("File {0} ({1:#,##0.00}MB) is larger than the specified limit of {2}MB, skipping file...", sourceFile, ((float)sourceFileWithSizeAndAttributes.size / 1024) / 1024, ignoreFilesLargerThan));
                                }

                                counter++;
                                counterSize += sourceFileWithSizeAndAttributes.size;
                                labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format(numberFormatInfo, "Status: Analyzing for files present in both source and destination directories. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, sourceCount, ((counterSize / 1024f) / 1024f) / 1024f, ((sourceSizeInBytes / 1024f) / 1024f) / 1024f); });
                                progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceSizeInBytes != 0 ? (int)((counterSize * 1000L) / sourceSizeInBytes) : 0; });
                            }
                        }
                    }

                    TimeSpan analysisTimeElapsed = DateTime.Now.Subtract(startTimeBackup);
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Analysis complete. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", analysisTimeElapsed.Hours, analysisTimeElapsed.Minutes, analysisTimeElapsed.Seconds, analysisTimeElapsed.Milliseconds)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of directories in the source directory: {0}", sourceDirectoryList.Count)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of directories in the destination directory: {0}", destinationDirectoryList.Count)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of directories present in the source directory but missing in the destination directory: {0}", directoriesMissingInDestination.Count)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of directories present in the destination directory but missing in the source directory: {0}", directoriesMissingInSource.Count)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of directories present in both source and destination directories but with different attributes: {0}", directoriesPresentWithAttributesMismatch.Count)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of files in the source directory: {0}", sourceFileList.Count)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of files in the destination directory: {0}", destinationFileList.Count)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of files present in the source directory but missing in the destination directory: {0}", filesMissingInDestination.Count)); });
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of files present in the destination directory but missing in the source directory: {0}", filesMissingInSource.Count)); });
                    if (ignoreFilesPresentInBothDirectories == false)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of files present in both source and destination directories, having the same hash: {0}", filesPresentWithHashMatch.Count)); });
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of files present in both source and destination directories, with different hashes: {0}", filesPresentWithHashMismatch.Count)); });
                    }
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Number of files present in both source and destination directories but with different attributes: {0}", filesPresentWithAttributesMismatch.Count)); });
                    outputLog.WriteLine("Analysis complete. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", analysisTimeElapsed.Hours, analysisTimeElapsed.Minutes, analysisTimeElapsed.Seconds, analysisTimeElapsed.Milliseconds));
                    outputLog.WriteLine(String.Format("Number of directories in the source directory: {0}", sourceDirectoryList.Count));
                    outputLog.WriteLine(String.Format("Number of directories in the destination directory: {0}", destinationDirectoryList.Count));
                    outputLog.WriteLine(String.Format("Number of directories present in the source directory but missing in the destination directory: {0}", directoriesMissingInDestination.Count));
                    outputLog.WriteLine(String.Format("Number of directories present in the destination directory but missing in the source directory: {0}", directoriesMissingInSource.Count));
                    outputLog.WriteLine(String.Format("Number of directories present in both source and destination directories but with different attributes: {0}", directoriesPresentWithAttributesMismatch.Count));
                    outputLog.WriteLine(String.Format("Number of files in the source directory: {0}", sourceFileList.Count));
                    outputLog.WriteLine(String.Format("Number of files in the destination directory: {0}", destinationFileList.Count));
                    outputLog.WriteLine(String.Format("Number of files present in the source directory but missing in the destination directory: {0}", filesMissingInDestination.Count));
                    outputLog.WriteLine(String.Format("Number of files present in the destination directory but missing in the source directory: {0}", filesMissingInSource.Count));
                    if (ignoreFilesPresentInBothDirectories == false)
                    {
                        outputLog.WriteLine(String.Format("Number of files present in both source and destination directories, having the same hash: {0}", filesPresentWithHashMatch.Count));
                        outputLog.WriteLine(String.Format("Number of files present in both source and destination directories, with different hashes: {0}", filesPresentWithHashMismatch.Count));
                    }
                    outputLog.WriteLine(String.Format("Number of files present in both source and destination directories but with different attributes: {0}", filesPresentWithAttributesMismatch.Count));
                    this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, "Analysis finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", analysisTimeElapsed.Hours, analysisTimeElapsed.Minutes, analysisTimeElapsed.Seconds, analysisTimeElapsed.Milliseconds), "Analysis information", MessageBoxButtons.OK, MessageBoxIcon.Information); });

                    if (directoriesMissingInSource.Count > 0)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Asking the user about the deletion of directories present in the destination directory but missing in the source directory..."); });
                        outputLog.WriteLine("Asking the user about the deletion of directories present in the destination directory but missing in the source directory...");
                        dialogResult = (DialogResult)this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, String.Format("There are directories present in the destination directory but missing in the source directory. Do you want to delete these directories?\r\n\r\nYou can review a list of these directories at: {0}\r\n\r\nWARNING: this cannot be undone, check the directories before proceeding.", outputLogDirectoriesMissingInSourcePath), "Confirm directory deletion operation", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); });
                        if (dialogResult == DialogResult.Yes)
                        {
                            DateTime startTimeDeleteOldDirectories = DateTime.Now;
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Directory deletion operation started..."); });
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Directory deletion start time: {0:00}:{1:00}:{2:00}", startTimeDeleteOldDirectories.Hour, startTimeDeleteOldDirectories.Minute, startTimeDeleteOldDirectories.Second)); });
                            outputLog.WriteLine("Directory deletion operation started...");
                            outputLog.WriteLine(String.Format("Directory deletion start time: {0:00}:{1:00}:{2:00}", startTimeDeleteOldDirectories.Hour, startTimeDeleteOldDirectories.Minute, startTimeDeleteOldDirectories.Second));

                            int destinationCount = directoriesMissingInSource.Count;
                            int counter = 0;

                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Deleting directories present in the destination directory but missing in the source directory. {0}/{1} directories", counter, destinationCount); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = destinationCount != 0 ? (int)((counter * 1000L) / destinationCount) : 0; });

                            foreach (directoryWithAttributes destinationDirectoryDeletion in directoriesMissingInSource)
                            {
                                if (cancelToken.Token.IsCancellationRequested)
                                {
                                    outputLog.WriteLine("Stopping backup...");
                                    cancelToken.Token.ThrowIfCancellationRequested();
                                }

                                if (Directory.Exists(destinationDirectoryDeletion.directory))
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Deleting directory: {0}", destinationDirectoryDeletion.directory)); });
                                    outputLog.WriteLine(String.Format("Deleting directory: {0}", destinationDirectoryDeletion.directory));
                                    DeleteRecursiveFolder(destinationDirectoryDeletion.directory);
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Directory deletion successful."); });
                                    outputLog.WriteLine("Directory deletion successful.");
                                }
                                else
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Directory {0} doesn't exist, skipping...", destinationDirectoryDeletion.directory)); });
                                    outputLog.WriteLine(String.Format("Directory {0} doesn't exist, skipping...", destinationDirectoryDeletion.directory));
                                }

                                counter++;
                                labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Deleting directories present in the destination directory but missing in the source directory. {0}/{1} directories", counter, destinationCount); });
                                progressBarShared.Invoke((Action)delegate { progressBarShared.Value = destinationCount != 0 ? (int)((counter * 1000L) / destinationCount) : 0; });
                            }

                            TimeSpan deleteOldDirectoriesTimeElapsed = DateTime.Now.Subtract(startTimeDeleteOldDirectories);
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Directory deletion operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", deleteOldDirectoriesTimeElapsed.Hours, deleteOldDirectoriesTimeElapsed.Minutes, deleteOldDirectoriesTimeElapsed.Seconds, deleteOldDirectoriesTimeElapsed.Milliseconds)); });
                            outputLog.WriteLine("Directory deletion operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", deleteOldDirectoriesTimeElapsed.Hours, deleteOldDirectoriesTimeElapsed.Minutes, deleteOldDirectoriesTimeElapsed.Seconds, deleteOldDirectoriesTimeElapsed.Milliseconds));
                        }
                        else
                        {
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Directory deletion operation skipped."); });
                            outputLog.WriteLine("Directory deletion operation skipped.");
                        }
                    }
                    else
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("There aren't any directories present in the destination directory but missing in the source directory, skipping directory deletion."); });
                        outputLog.WriteLine("There aren't any directories present in the destination directory but missing in the source directory, skipping directory deletion.");
                    }

                    if (directoriesMissingInDestination.Count > 0)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Asking the user about the creation of new directories..."); });
                        outputLog.WriteLine("Asking the user about the creation of new directories...");
                        dialogResult = (DialogResult)this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, String.Format("There are directories present in the source directory but missing in the destination directory. Do you want to create these directories in the destination directory?\r\n\r\nYou can review a list of these directories at: {0}", outputLogDirectoriesMissingInDestinationPath), "Confirm directory creation operation", MessageBoxButtons.YesNo, MessageBoxIcon.Question); });
                        if (dialogResult == DialogResult.Yes)
                        {
                            DateTime startTimeCreateNewDirectories = DateTime.Now;
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Directory creation operation started..."); });
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Directory creation start time: {0:00}:{1:00}:{2:00}", startTimeCreateNewDirectories.Hour, startTimeCreateNewDirectories.Minute, startTimeCreateNewDirectories.Second)); });
                            outputLog.WriteLine("Directory creation operation started...");
                            outputLog.WriteLine(String.Format("Directory creation start time: {0:00}:{1:00}:{2:00}", startTimeCreateNewDirectories.Hour, startTimeCreateNewDirectories.Minute, startTimeCreateNewDirectories.Second));

                            int sourceCount = directoriesMissingInDestination.Count;
                            int counter = 0;

                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Creating directories present in the source directory but missing in the destination directory. {0}/{1} directories", counter, sourceCount); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceCount != 0 ? (int)((counter * 1000L) / sourceCount) : 0; });

                            foreach (directoryWithAttributes sourceDirectoryCreation in directoriesMissingInDestination)
                            {
                                if (cancelToken.Token.IsCancellationRequested)
                                {
                                    outputLog.WriteLine("Stopping backup...");
                                    cancelToken.Token.ThrowIfCancellationRequested();
                                }

                                string destinationDirectoryCreation = sourceDirectoryCreation.directory.Replace(sourceDirectory, destinationDirectory);
                                if (Directory.Exists(destinationDirectoryCreation))
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Directory {0} already exists, skipping...", destinationDirectoryCreation)); });
                                    outputLog.WriteLine(String.Format("Directory {0} already exists, skipping...", destinationDirectoryCreation));
                                }
                                else
                                {
                                    if (Directory.Exists(sourceDirectoryCreation.directory))
                                    {
                                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Creating directory: {0}", destinationDirectoryCreation)); });
                                        outputLog.WriteLine(String.Format("Creating directory: {0}", destinationDirectoryCreation));
                                        Directory.CreateDirectory(destinationDirectoryCreation);
                                        Directory.SetCreationTime(destinationDirectoryCreation, Directory.GetCreationTime(sourceDirectoryCreation.directory));
                                        Directory.SetLastAccessTime(destinationDirectoryCreation, Directory.GetLastAccessTime(sourceDirectoryCreation.directory));
                                        Directory.SetLastWriteTime(destinationDirectoryCreation, Directory.GetLastWriteTime(sourceDirectoryCreation.directory));
                                        DirectoryInfo directoryInfo = new DirectoryInfo(destinationDirectoryCreation);
                                        directoryInfo.Attributes = sourceDirectoryCreation.attributes;
                                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Directory creation successful."); });
                                        outputLog.WriteLine("Directory creation successful.");
                                    }
                                    else
                                    {
                                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: Directory {0} doesn't exist, skipping...", sourceDirectoryCreation.directory)); });
                                        outputLog.WriteLine(String.Format("WARNING: Directory {0} doesn't exist, skipping...", sourceDirectoryCreation.directory));
                                    }
                                }

                                counter++;
                                labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Creating directories present in the source directory but missing in the destination directory. {0}/{1} directories", counter, sourceCount); });
                                progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceCount != 0 ? (int)((counter * 1000L) / sourceCount) : 0; });
                            }

                            TimeSpan createNewDirectoriesTimeElapsed = DateTime.Now.Subtract(startTimeCreateNewDirectories);
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Directory creation operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", createNewDirectoriesTimeElapsed.Hours, createNewDirectoriesTimeElapsed.Minutes, createNewDirectoriesTimeElapsed.Seconds, createNewDirectoriesTimeElapsed.Milliseconds)); });
                            outputLog.WriteLine("Directory creation operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", createNewDirectoriesTimeElapsed.Hours, createNewDirectoriesTimeElapsed.Minutes, createNewDirectoriesTimeElapsed.Seconds, createNewDirectoriesTimeElapsed.Milliseconds));
                        }
                        else
                        {
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Directory creation operation skipped."); });
                            outputLog.WriteLine("Directory creation operation skipped.");
                        }
                    }
                    else
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("There aren't any directories present in the source directory but missing in the destination directory, skipping directory creation."); });
                        outputLog.WriteLine("There aren't any directories present in the source directory but missing in the destination directory, skipping directory creation.");
                    }

                    if (filesMissingInSource.Count > 0)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Asking the user about the deletion of files present in the destination directory but missing in the source directory..."); });
                        outputLog.WriteLine("Asking the user about the deletion of files present in the destination directory but missing in the source directory...");
                        dialogResult = (DialogResult)this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, String.Format("There are files present in the destination directory but missing in the source directory. Do you want to delete these files?\r\n\r\nYou can review a list of these files at: {0}\r\n\r\nWARNING: this cannot be undone, check the files before proceeding.", outputLogFilesMissingInSourcePath), "Confirm file deletion operation", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); });
                        if (dialogResult == DialogResult.Yes)
                        {
                            DateTime startTimeDeleteOldFiles = DateTime.Now;
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File deletion operation started..."); });
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("File deletion start time: {0:00}:{1:00}:{2:00}", startTimeDeleteOldFiles.Hour, startTimeDeleteOldFiles.Minute, startTimeDeleteOldFiles.Second)); });
                            outputLog.WriteLine("File deletion operation started...");
                            outputLog.WriteLine(String.Format("File deletion start time: {0:00}:{1:00}:{2:00}", startTimeDeleteOldFiles.Hour, startTimeDeleteOldFiles.Minute, startTimeDeleteOldFiles.Second));

                            int destinationCount = filesMissingInSource.Count;
                            int counter = 0;

                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Deleting files present in the destination directory but missing in the source directory. {0}/{1} files", counter, destinationCount); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = destinationCount != 0 ? (int)((counter * 1000L) / destinationCount) : 0; });

                            foreach (fileWithSizeAndAttributes destinationFileWithSizeAndAttributes in filesMissingInSource)
                            {
                                string destinationFile = destinationFileWithSizeAndAttributes.file;
                                string destinationDirectoryDelete = Path.GetDirectoryName(destinationFile);

                                if (cancelToken.Token.IsCancellationRequested)
                                {
                                    outputLog.WriteLine("Stopping backup...");
                                    cancelToken.Token.ThrowIfCancellationRequested();
                                }

                                if (File.Exists(destinationFile))
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Deleting file: {0}", destinationFile)); });
                                    outputLog.WriteLine(String.Format("Deleting file: {0}", destinationFile));
                                    File.SetAttributes(destinationFile, FileAttributes.Normal);
                                    File.Delete(destinationFile);
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File deletion successful."); });
                                    outputLog.WriteLine("File deletion successful.");
                                }
                                else
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("File {0} doesn't exist, skipping file...", destinationFile)); });
                                    outputLog.WriteLine(String.Format("File {0} doesn't exist, skipping file...", destinationFile));
                                }

                                counter++;
                                labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Deleting files present in the destination directory but missing in the source directory. {0}/{1} files", counter, destinationCount); });
                                progressBarShared.Invoke((Action)delegate { progressBarShared.Value = destinationCount != 0 ? (int)((counter * 1000L) / destinationCount) : 0; });
                            }

                            TimeSpan deleteOldFilesTimeElapsed = DateTime.Now.Subtract(startTimeDeleteOldFiles);
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File deletion operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", deleteOldFilesTimeElapsed.Hours, deleteOldFilesTimeElapsed.Minutes, deleteOldFilesTimeElapsed.Seconds, deleteOldFilesTimeElapsed.Milliseconds)); });
                            outputLog.WriteLine("File deletion operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", deleteOldFilesTimeElapsed.Hours, deleteOldFilesTimeElapsed.Minutes, deleteOldFilesTimeElapsed.Seconds, deleteOldFilesTimeElapsed.Milliseconds));
                        }
                        else
                        {
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File deletion operation skipped."); });
                            outputLog.WriteLine("File deletion operation skipped.");
                        }
                    }
                    else
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("There aren't any files present in the destination directory but missing in the source directory, skipping file deletion."); });
                        outputLog.WriteLine("There aren't any files present in the destination directory but missing in the source directory, skipping file deletion.");
                    }

                    if (filesMissingInDestination.Count > 0)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Asking the user about the copy of files present in the source directory but missing in the destination directory..."); });
                        outputLog.WriteLine("Asking the user about the copy of files present in the source directory but missing in the destination directory...");
                        dialogResult = (DialogResult)this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, String.Format("There are files present in the source directory but missing in the destination directory. Do you want to copy these files to the destination directory?\r\n\r\nYou can review a list of these files at: {0}", outputLogFilesMissingInDestinationPath), "Confirm file copy operation", MessageBoxButtons.YesNo, MessageBoxIcon.Question); });
                        if (dialogResult == DialogResult.Yes)
                        {
                            DateTime startTimeCopyNewFiles = DateTime.Now;
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File copy operation started..."); });
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("File copy start time: {0:00}:{1:00}:{2:00}", startTimeCopyNewFiles.Hour, startTimeCopyNewFiles.Minute, startTimeCopyNewFiles.Second)); });
                            outputLog.WriteLine("File copy operation started...");
                            outputLog.WriteLine(String.Format("File copy start time: {0:00}:{1:00}:{2:00}", startTimeCopyNewFiles.Hour, startTimeCopyNewFiles.Minute, startTimeCopyNewFiles.Second));

                            Int64 filesMissingInDestinationSize = 0;
                            int sourceCount = filesMissingInDestination.Count;
                            Int64 counterSize = 0;
                            int counter = 0;
                            foreach (fileWithSizeAndAttributes sourceFile in filesMissingInDestination)
                            {
                                filesMissingInDestinationSize += sourceFile.size;
                            }

                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Copying files present in the source directory but missing in the destination directory. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, sourceCount, ((counterSize / 1024f) / 1024f) / 1024f, ((filesMissingInDestinationSize / 1024f) / 1024f) / 1024f); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = filesMissingInDestinationSize != 0 ? (int)((counterSize * 1000L) / filesMissingInDestinationSize) : 0; });

                            bool hashErrors = false;
                            foreach (fileWithSizeAndAttributes sourceFileWithSizeAndAttributes in filesMissingInDestination)
                            {
                                string sourceFile = sourceFileWithSizeAndAttributes.file;
                                if (cancelToken.Token.IsCancellationRequested)
                                {
                                    outputLog.WriteLine("Stopping backup...");
                                    cancelToken.Token.ThrowIfCancellationRequested();
                                }

                                string destinationFile = sourceFile.Replace(sourceDirectory, destinationDirectory);
                                string destinationDirectoryCopy = Path.GetDirectoryName(destinationFile);
                                string sourceDirectoryCopy = Path.GetDirectoryName(sourceFile);
                                if (Directory.Exists(destinationDirectoryCopy))
                                {
                                    if (File.Exists(sourceFile))
                                    {
                                        bool retryHash = true;
                                        for (int i = 1; i <= maximumNumAttempts && retryHash == true; i++)
                                        {
                                            if (cancelToken.Token.IsCancellationRequested)
                                            {
                                                outputLog.WriteLine("Stopping backup...");
                                                cancelToken.Token.ThrowIfCancellationRequested();
                                            }

                                            if (i > 1 && File.Exists(destinationFile))
                                            {
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Deleting file: {0}", destinationFile)); });
                                                outputLog.WriteLine(String.Format("Deleting file: {0}", destinationFile));
                                                File.SetAttributes(destinationFile, FileAttributes.Normal);
                                                File.Delete(destinationFile);
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File deletion successful."); });
                                                outputLog.WriteLine("File deletion successful.");
                                            }

                                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Copying file: {0}", sourceFile)); });
                                            outputLog.WriteLine(String.Format("Copying file: {0}", sourceFile));

                                            File.Copy(sourceFile, destinationFile, false);
                                            if ((File.GetAttributes(destinationFile) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                                            {
                                                File.SetAttributes(destinationFile, File.GetAttributes(destinationFile) & ~FileAttributes.ReadOnly);
                                                File.SetCreationTime(destinationFile, File.GetCreationTime(sourceFile));
                                                File.SetLastAccessTime(destinationFile, File.GetLastAccessTime(sourceFile));
                                                File.SetLastWriteTime(destinationFile, File.GetLastWriteTime(sourceFile));
                                                File.SetAttributes(destinationFile, File.GetAttributes(destinationFile) | FileAttributes.ReadOnly);
                                            }
                                            else
                                            {
                                                File.SetCreationTime(destinationFile, File.GetCreationTime(sourceFile));
                                                File.SetLastAccessTime(destinationFile, File.GetLastAccessTime(sourceFile));
                                                File.SetLastWriteTime(destinationFile, File.GetLastWriteTime(sourceFile));
                                            }

                                            sourceAndDestinationHashes hashes = calculateHashesParallel(sourceFile, destinationFile);

                                            //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Source file {0}, hash: {1}", sourceFile, hashes.sourceHash)); });
                                            outputLog.WriteLine(String.Format("Source file {0}, hash: {1}", sourceFile, hashes.sourceHash));

                                            //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Destination file {0}, hash: {1}", destinationFile, hashes.destinationHash)); });
                                            outputLog.WriteLine(String.Format("Destination file {0}, hash: {1}", destinationFile, hashes.destinationHash));

                                            if (hashes.sourceHash == hashes.destinationHash)
                                            {
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File hashes match."); });
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File copy successful."); });
                                                outputLog.WriteLine("File hashes match.");
                                                outputLog.WriteLine("File copy successful.");
                                                retryHash = false;
                                            }
                                            else if (i < maximumNumAttempts)
                                            {
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog("WARNING: File hashes do not match after file copying, retrying..."); });
                                                outputLog.WriteLine("WARNING: File hashes do not match after file copying, retrying...");
                                            }
                                            else
                                            {
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("ERROR: File hashes do not match after {0} attemps, file ignored.", maximumNumAttempts)); });
                                                outputLog.WriteLine(String.Format("ERROR: File hashes do not match after {0} attemps, file ignored.", maximumNumAttempts));
                                                hashErrors = true;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: File {0} doesn't exist, skipping file copy...", sourceFile)); });
                                        outputLog.WriteLine(String.Format("WARNING: File {0} doesn't exist, skipping file copy...", sourceFile));
                                    }
                                }
                                else
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Directory {0} doesn't exist, skipping file copy...", destinationDirectoryCopy)); });
                                    outputLog.WriteLine(String.Format("Directory {0} doesn't exist, skipping file copy...", destinationDirectoryCopy));
                                }

                                counter++;
                                counterSize += sourceFileWithSizeAndAttributes.size;
                                labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Copying files present in the source directory but missing in the destination directory. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, sourceCount, ((counterSize / 1024f) / 1024f) / 1024f, ((filesMissingInDestinationSize / 1024f) / 1024f) / 1024f); });
                                progressBarShared.Invoke((Action)delegate { progressBarShared.Value = filesMissingInDestinationSize != 0 ? (int)((counterSize * 1000L) / filesMissingInDestinationSize) : 0; });
                            }

                            TimeSpan copyNewFilesTimeElapsed = DateTime.Now.Subtract(startTimeCopyNewFiles);
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File copy operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", copyNewFilesTimeElapsed.Hours, copyNewFilesTimeElapsed.Minutes, copyNewFilesTimeElapsed.Seconds, copyNewFilesTimeElapsed.Milliseconds)); });
                            outputLog.WriteLine("File copy operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", copyNewFilesTimeElapsed.Hours, copyNewFilesTimeElapsed.Minutes, copyNewFilesTimeElapsed.Seconds, copyNewFilesTimeElapsed.Milliseconds));
                            if (hashErrors == true)
                            {
                                this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, "File copy operation finished with errors. Check the log for more information (lines starting with ERROR).", "File copy information", MessageBoxButtons.OK, MessageBoxIcon.Error); });
                            }
                        }
                        else
                        {
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File copy operation skipped."); });
                            outputLog.WriteLine("File copy operation skipped.");
                        }
                    }
                    else
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("There aren't any files present in the source directory but missing in the destination directory, skipping file copy."); });
                        outputLog.WriteLine("There aren't any files present in the source directory but missing in the destination directory, skipping file copy.");
                    }

                    if (filesPresentWithHashMismatch.Count > 0 && ignoreFilesPresentInBothDirectories == false)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Asking the user about the replacing of files present in both source and destination directories but with different hashes..."); });
                        outputLog.WriteLine("Asking the user about the replacing of files present in both source and destination directories but with different hashes...");
                        dialogResult = (DialogResult)this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, String.Format("There are files present in both source and destination directories but with different hashes. Do you want to replace the destination files with the source files?\r\n\r\nYou can review a list of these files at: {0}\r\n\r\nWARNING: this cannot be undone, check the files before proceeding.", outputLogFilesPresentWithHashMismatchPath), "Confirm file replacing operation", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); });
                        if (dialogResult == DialogResult.Yes)
                        {
                            DateTime startTimeReplaceFiles = DateTime.Now;
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File replacing operation started..."); });
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("File replacing start time: {0:00}:{1:00}:{2:00}", startTimeReplaceFiles.Hour, startTimeReplaceFiles.Minute, startTimeReplaceFiles.Second)); });
                            outputLog.WriteLine("File replacing operation started...");
                            outputLog.WriteLine(String.Format("File replacing start time: {0:00}:{1:00}:{2:00}", startTimeReplaceFiles.Hour, startTimeReplaceFiles.Minute, startTimeReplaceFiles.Second));

                            Int64 filesPresentWithHashMismatchSize = 0;
                            int sourceCount = filesPresentWithHashMismatch.Count;
                            Int64 counterSize = 0;
                            int counter = 0;
                            foreach (fileWithSizeAndAttributes sourceFile in filesPresentWithHashMismatch)
                            {
                                filesPresentWithHashMismatchSize += sourceFile.size;
                            }

                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Replacing files present in both source and destination directories but with different hashes. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, sourceCount, ((counterSize / 1024f) / 1024f) / 1024f, ((filesPresentWithHashMismatchSize / 1024f) / 1024f) / 1024f); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = filesPresentWithHashMismatchSize != 0 ? (int)((counterSize * 1000L) / filesPresentWithHashMismatchSize) : 0; });

                            bool hashErrors = false;
                            foreach (fileWithSizeAndAttributes sourceFileWithSizeAndAttributes in filesPresentWithHashMismatch)
                            {
                                string sourceFile = sourceFileWithSizeAndAttributes.file;
                                if (cancelToken.Token.IsCancellationRequested)
                                {
                                    outputLog.WriteLine("Stopping backup...");
                                    cancelToken.Token.ThrowIfCancellationRequested();
                                }

                                string destinationFile = sourceFile.Replace(sourceDirectory, destinationDirectory);
                                if (File.Exists(sourceFile))
                                {
                                    if (File.Exists(destinationFile))
                                    {
                                        bool retryHash = true;
                                        for (int i = 1; i <= maximumNumAttempts && retryHash == true; i++)
                                        {
                                            if (cancelToken.Token.IsCancellationRequested)
                                            {
                                                outputLog.WriteLine("Stopping backup...");
                                                cancelToken.Token.ThrowIfCancellationRequested();
                                            }

                                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Deleting file: {0}", destinationFile)); });
                                            outputLog.WriteLine(String.Format("Deleting file: {0}", destinationFile));
                                            File.SetAttributes(destinationFile, FileAttributes.Normal);
                                            File.Delete(destinationFile);
                                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File deletion successful."); });
                                            outputLog.WriteLine("File deletion successful.");

                                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Replacing with file: {0}", sourceFile)); });
                                            outputLog.WriteLine(String.Format("Replacing with file: {0}", sourceFile));

                                            File.Copy(sourceFile, destinationFile, false);
                                            if ((File.GetAttributes(destinationFile) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                                            {
                                                File.SetAttributes(destinationFile, File.GetAttributes(destinationFile) & ~FileAttributes.ReadOnly);
                                                File.SetCreationTime(destinationFile, File.GetCreationTime(sourceFile));
                                                File.SetLastAccessTime(destinationFile, File.GetLastAccessTime(sourceFile));
                                                File.SetLastWriteTime(destinationFile, File.GetLastWriteTime(sourceFile));
                                                File.SetAttributes(destinationFile, File.GetAttributes(destinationFile) | FileAttributes.ReadOnly);
                                            }
                                            else
                                            {
                                                File.SetCreationTime(destinationFile, File.GetCreationTime(sourceFile));
                                                File.SetLastAccessTime(destinationFile, File.GetLastAccessTime(sourceFile));
                                                File.SetLastWriteTime(destinationFile, File.GetLastWriteTime(sourceFile));
                                            }

                                            sourceAndDestinationHashes hashes = calculateHashesParallel(sourceFile, destinationFile);

                                            //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Source file {0}, hash: {1}", sourceFile, hashes.sourceHash)); });
                                            outputLog.WriteLine(String.Format("Source file {0}, hash: {1}", sourceFile, hashes.sourceHash));

                                            //textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Destination file {0}, hash: {1}", destinationFile, hashes.destinationHash)); });
                                            outputLog.WriteLine(String.Format("Destination file {0}, hash: {1}", destinationFile, hashes.destinationHash));

                                            if (hashes.sourceHash == hashes.destinationHash)
                                            {
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File hashes match."); });
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File replacing successful."); });
                                                outputLog.WriteLine("File hashes match.");
                                                outputLog.WriteLine("File replacing successful.");
                                                retryHash = false;
                                            }
                                            else if (i < maximumNumAttempts)
                                            {
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog("WARNING: File hashes do not match after file replacing, retrying..."); });
                                                outputLog.WriteLine("WARNING: File hashes do not match after file replacing, retrying...");
                                            }
                                            else
                                            {
                                                textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("ERROR: File hashes do not match after {0} attemps, file ignored.", maximumNumAttempts)); });
                                                outputLog.WriteLine(String.Format("ERROR: File hashes do not match after {0} attemps, file ignored.", maximumNumAttempts));
                                                hashErrors = true;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: File {0} doesn't exist, skipping file...", destinationFile)); });
                                        outputLog.WriteLine(String.Format("WARNING: File {0} doesn't exist, skipping file...", destinationFile));
                                    }
                                }
                                else
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("WARNING: File {0} doesn't exist, skipping file...", sourceFile)); });
                                    outputLog.WriteLine(String.Format("WARNING: File {0} doesn't exist, skipping file...", sourceFile));
                                }

                                counter++;
                                counterSize += sourceFileWithSizeAndAttributes.size;
                                labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Replacing files present in both source and destination directories but with different hashes. {0}/{1} files, {2:#,##0.00}GB/{3:#,##0.00}GB", counter, sourceCount, ((counterSize / 1024f) / 1024f) / 1024f, ((filesPresentWithHashMismatchSize / 1024f) / 1024f) / 1024f); });
                                progressBarShared.Invoke((Action)delegate { progressBarShared.Value = filesPresentWithHashMismatchSize != 0 ? (int)((counterSize * 1000L) / filesPresentWithHashMismatchSize) : 0; });
                            }

                            TimeSpan replaceFilesTimeElapsed = DateTime.Now.Subtract(startTimeReplaceFiles);
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File replacing operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", replaceFilesTimeElapsed.Hours, replaceFilesTimeElapsed.Minutes, replaceFilesTimeElapsed.Seconds, replaceFilesTimeElapsed.Milliseconds)); });
                            outputLog.WriteLine("File replacing operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", replaceFilesTimeElapsed.Hours, replaceFilesTimeElapsed.Minutes, replaceFilesTimeElapsed.Seconds, replaceFilesTimeElapsed.Milliseconds));
                            if (hashErrors == true)
                            {
                                this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, "File replacing operation finished with errors. Check the log for more information (lines starting with ERROR).", "File replacing information", MessageBoxButtons.OK, MessageBoxIcon.Error); });
                            }
                        }
                        else
                        {
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("File replacing operation skipped."); });
                            outputLog.WriteLine("File replacing operation skipped.");
                        }
                    }
                    else if (ignoreFilesPresentInBothDirectories == false)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("There aren't any files present in both source and destination directories but with different hashes, skipping file replacing."); });
                        outputLog.WriteLine("There aren't any files present in both source and destination directories but with different hashes, skipping file replacing.");
                    }

                    if (filesPresentWithAttributesMismatch.Count > 0)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Asking the user about the replacing of attributes in files present in both directories but with different attributes..."); });
                        outputLog.WriteLine("Asking the user about the replacing of attributes in files present in both directories but with different attributes...");
                        dialogResult = (DialogResult)this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, String.Format("There are files present in both the source and destination directories but with different attributes. Do you want to replace these attributes?\r\n\r\nYou can review a list of these files at: {0}\r\n\r\nWARNING: this cannot be undone, check the files before proceeding.", outputLogFilesPresentWithAttributesMismatchPath), "Confirm attribute replacing operation", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); });
                        if (dialogResult == DialogResult.Yes)
                        {
                            DateTime startTimeReplaceOldFileAttributes = DateTime.Now;
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Attribute replacing operation started..."); });
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Attribute replacing start time: {0:00}:{1:00}:{2:00}", startTimeReplaceOldFileAttributes.Hour, startTimeReplaceOldFileAttributes.Minute, startTimeReplaceOldFileAttributes.Second)); });
                            outputLog.WriteLine("Attribute replacing operation started...");
                            outputLog.WriteLine(String.Format("Attribute replacing start time: {0:00}:{1:00}:{2:00}", startTimeReplaceOldFileAttributes.Hour, startTimeReplaceOldFileAttributes.Minute, startTimeReplaceOldFileAttributes.Second));

                            int sourceCount = filesPresentWithAttributesMismatch.Count;
                            int counter = 0;

                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Replacing attributes in files present in both source and destination directories but with different attributes. {0}/{1} files", counter, sourceCount); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceCount != 0 ? (int)((counter * 1000L) / sourceCount) : 0; });

                            foreach (fileWithSizeAndAttributes sourceFile in filesPresentWithAttributesMismatch)
                            {
                                string destinationFile = sourceFile.file.Replace(sourceDirectory, destinationDirectory);
                                if (cancelToken.Token.IsCancellationRequested)
                                {
                                    outputLog.WriteLine("Stopping backup...");
                                    cancelToken.Token.ThrowIfCancellationRequested();
                                }

                                if (File.Exists(destinationFile) && File.Exists(sourceFile.file))
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Replacing attributes in file: {0}", destinationFile)); });
                                    outputLog.WriteLine(String.Format("Replacing attributes in file: {0}", destinationFile));
                                    FileInfo fileInfo = new FileInfo(destinationFile);
                                    fileInfo.Attributes = sourceFile.attributes;
                                    File.SetCreationTime(destinationFile, File.GetCreationTime(sourceFile.file));
                                    File.SetLastAccessTime(destinationFile, File.GetLastAccessTime(sourceFile.file));
                                    File.SetLastWriteTime(destinationFile, File.GetLastWriteTime(sourceFile.file));
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Attribute replacing successful."); });
                                    outputLog.WriteLine("Attribute replacing successful.");
                                }
                                else
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("File {0} doesn't exist, skipping...", sourceFile.file)); });
                                    outputLog.WriteLine(String.Format("File {0} doesn't exist, skipping...", sourceFile.file));
                                }

                                counter++;
                                labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Replacing attributes in files present in both source and destination directories but with different attributes. {0}/{1} files", counter, sourceCount); });
                                progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceCount != 0 ? (int)((counter * 1000L) / sourceCount) : 0; });
                            }

                            TimeSpan replaceOldFileAttributesTimeElapsed = DateTime.Now.Subtract(startTimeReplaceOldFileAttributes);
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Attribute replacing operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", replaceOldFileAttributesTimeElapsed.Hours, replaceOldFileAttributesTimeElapsed.Minutes, replaceOldFileAttributesTimeElapsed.Seconds, replaceOldFileAttributesTimeElapsed.Milliseconds)); });
                            outputLog.WriteLine("Attribute replacing operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", replaceOldFileAttributesTimeElapsed.Hours, replaceOldFileAttributesTimeElapsed.Minutes, replaceOldFileAttributesTimeElapsed.Seconds, replaceOldFileAttributesTimeElapsed.Milliseconds));
                        }
                        else
                        {
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Attribute replacing operation skipped."); });
                            outputLog.WriteLine("Attribute replacing operation skipped.");
                        }
                    }
                    else
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("There aren't any files present in both the source and destination directories but with different attributes, skipping attribute replacing."); });
                        outputLog.WriteLine("There aren't any files present in both the source and destination directories but with different attributes, skipping attribute replacing.");
                    }

                    if (directoriesPresentWithAttributesMismatch.Count > 0)
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Asking the user about the replacing of attributes in directories present in both directories but with different attributes..."); });
                        outputLog.WriteLine("Asking the user about the replacing of attributes in directories present in both directories but with different attributes...");
                        dialogResult = (DialogResult)this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, String.Format("There are directories present in both the source and destination directories but with different attributes. Do you want to replace these attributes?\r\n\r\nYou can review a list of these directories at: {0}\r\n\r\nWARNING: this cannot be undone, check the directories before proceeding.", outputLogDirectoriesPresentWithAttributesMismatchPath), "Confirm attribute replacing operation", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); });
                        if (dialogResult == DialogResult.Yes)
                        {
                            DateTime startTimeReplaceOldDirectoryAttributes = DateTime.Now;
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Attribute replacing operation started..."); });
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Attribute replacing start time: {0:00}:{1:00}:{2:00}", startTimeReplaceOldDirectoryAttributes.Hour, startTimeReplaceOldDirectoryAttributes.Minute, startTimeReplaceOldDirectoryAttributes.Second)); });
                            outputLog.WriteLine("Attribute replacing operation started...");
                            outputLog.WriteLine(String.Format("Attribute replacing start time: {0:00}:{1:00}:{2:00}", startTimeReplaceOldDirectoryAttributes.Hour, startTimeReplaceOldDirectoryAttributes.Minute, startTimeReplaceOldDirectoryAttributes.Second));

                            int sourceCount = directoriesPresentWithAttributesMismatch.Count;
                            int counter = 0;

                            labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Replacing attributes in directories present in both source and destination directories but with different attributes. {0}/{1} directories", counter, sourceCount); });
                            progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceCount != 0 ? (int)((counter * 1000L) / sourceCount) : 0; });

                            foreach (directoryWithAttributes sourceDirectoryReplaceAttributes in directoriesPresentWithAttributesMismatch)
                            {
                                string destinationDirectoryReplaceAttributes = sourceDirectoryReplaceAttributes.directory.Replace(sourceDirectory, destinationDirectory);
                                if (cancelToken.Token.IsCancellationRequested)
                                {
                                    outputLog.WriteLine("Stopping backup...");
                                    cancelToken.Token.ThrowIfCancellationRequested();
                                }

                                if (Directory.Exists(destinationDirectoryReplaceAttributes) && Directory.Exists(sourceDirectoryReplaceAttributes.directory))
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Replacing attributes in directory: {0}", destinationDirectoryReplaceAttributes)); });
                                    outputLog.WriteLine(String.Format("Replacing attributes in directory: {0}", destinationDirectoryReplaceAttributes));
                                    DirectoryInfo directoryInfo = new DirectoryInfo(destinationDirectoryReplaceAttributes);
                                    directoryInfo.Attributes = sourceDirectoryReplaceAttributes.attributes;
                                    Directory.SetCreationTime(destinationDirectoryReplaceAttributes, Directory.GetCreationTime(sourceDirectoryReplaceAttributes.directory));
                                    Directory.SetLastAccessTime(destinationDirectoryReplaceAttributes, Directory.GetLastAccessTime(sourceDirectoryReplaceAttributes.directory));
                                    Directory.SetLastWriteTime(destinationDirectoryReplaceAttributes, Directory.GetLastWriteTime(sourceDirectoryReplaceAttributes.directory));
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Attribute replacing successful."); });
                                    outputLog.WriteLine("Attribute replacing successful.");
                                }
                                else
                                {
                                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog(String.Format("Directory {0} doesn't exist, skipping...", sourceDirectoryReplaceAttributes.directory)); });
                                    outputLog.WriteLine(String.Format("Directory {0} doesn't exist, skipping...", sourceDirectoryReplaceAttributes.directory));
                                }

                                counter++;
                                labelProgressBar.Invoke((Action)delegate { labelProgressBar.Text = String.Format("Status: Replacing attributes in directories present in both source and destination directories but with different attributes. {0}/{1} directories", counter, sourceCount); });
                                progressBarShared.Invoke((Action)delegate { progressBarShared.Value = sourceCount != 0 ? (int)((counter * 1000L) / sourceCount) : 0; });
                            }

                            TimeSpan replaceOldDirectoryAttributesTimeElapsed = DateTime.Now.Subtract(startTimeReplaceOldDirectoryAttributes);
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Attribute replacing operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", replaceOldDirectoryAttributesTimeElapsed.Hours, replaceOldDirectoryAttributesTimeElapsed.Minutes, replaceOldDirectoryAttributesTimeElapsed.Seconds, replaceOldDirectoryAttributesTimeElapsed.Milliseconds)); });
                            outputLog.WriteLine("Attribute replacing operation finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", replaceOldDirectoryAttributesTimeElapsed.Hours, replaceOldDirectoryAttributesTimeElapsed.Minutes, replaceOldDirectoryAttributesTimeElapsed.Seconds, replaceOldDirectoryAttributesTimeElapsed.Milliseconds));
                        }
                        else
                        {
                            textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Attribute replacing operation skipped."); });
                            outputLog.WriteLine("Attribute replacing operation skipped.");
                        }
                    }
                    else
                    {
                        textBoxLog.Invoke((Action)delegate { writeTextBoxLog("There aren't any directories present in both the source and destination directories but with different attributes, skipping attribute replacing."); });
                        outputLog.WriteLine("There aren't any directories present in both the source and destination directories but with different attributes, skipping attribute replacing.");
                    }

                    if (filesMissingInDestination.Count == 0 && filesMissingInSource.Count == 0 && filesPresentWithHashMismatch.Count == 0 && filesPresentWithAttributesMismatch.Count == 0 && directoriesMissingInDestination.Count == 0 && directoriesMissingInSource.Count == 0 && directoriesPresentWithAttributesMismatch.Count == 0)
                    {
                        this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, "The source and destination directories are in sync, no action required.", "Backup information", MessageBoxButtons.OK, MessageBoxIcon.Information); });
                    }
                    TimeSpan backupTimeElapsed = DateTime.Now.Subtract(startTimeBackup);
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Backup finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", backupTimeElapsed.Hours, backupTimeElapsed.Minutes, backupTimeElapsed.Seconds, backupTimeElapsed.Milliseconds)); });
                    outputLog.WriteLine("Backup finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", backupTimeElapsed.Hours, backupTimeElapsed.Minutes, backupTimeElapsed.Seconds, backupTimeElapsed.Milliseconds));
                    this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, "Backup finished. Time elapsed: " + String.Format("{0:00}:{1:00}:{2:00}.{3:000}", backupTimeElapsed.Hours, backupTimeElapsed.Minutes, backupTimeElapsed.Seconds, backupTimeElapsed.Milliseconds), "Backup information", MessageBoxButtons.OK, MessageBoxIcon.Information); });
                }
                catch (OperationCanceledException)
                {
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("Backup stopped successfully."); });
                    outputLog.WriteLine("Backup stopped successfully.");
                    this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, "Backup stopped successfully.", "Stop backup operation", MessageBoxButtons.OK, MessageBoxIcon.Information); });
                }
                catch (Exception ex)
                {
                    textBoxLog.Invoke((Action)delegate { writeTextBoxLog("ERROR: " + ex.Message); });
                    outputLog.WriteLine("ERROR: " + ex.Message);
                    this.Invoke((Func<DialogResult>)delegate { return MessageBox.Show(this, "ERROR: " + ex.Message, "Backup error", MessageBoxButtons.OK, MessageBoxIcon.Error); });
                }
                finally
                {
                    outputLog.Close();
                    checkBoxIgnorePresentInBoth.Invoke((Action)delegate { checkBoxIgnorePresentInBoth.Enabled = true; });
                    if (checkBoxIgnorePresentInBoth.CheckState != CheckState.Checked)
                    {
                        textBoxLargerThan.Invoke((Action)delegate { textBoxLargerThan.ReadOnly = false; });
                    }
                    buttonStart.Invoke((Action)delegate { buttonStart.Enabled = true; });
                    buttonStop.Invoke((Action)delegate { buttonStop.Enabled = false; });
                    buttonBrowseSource.Invoke((Action)delegate { buttonBrowseSource.Enabled = true; });
                    buttonBrowseDestination.Invoke((Action)delegate { buttonBrowseDestination.Enabled = true; });
                    textBoxSource.Invoke((Action)delegate { textBoxSource.ReadOnly = false; });
                    textBoxDestination.Invoke((Action)delegate { textBoxDestination.ReadOnly = false; });
                }

               // ContinueWith is used to sync the UI when the task completes
            }, cancelToken.Token).ContinueWith((result) =>
            {
                // Do stuff, like updating the main UI
            }, new CancellationTokenSource().Token, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
        }
    }
}
